
module DrunkenMaster
  module Util
    class AsciiTable
      attr_accessor :name, :columns, :rows
      DIVIDER = 'row-divider'

      def initialize(name='')
        @name = name
        @columns = []
        @rows = []
      end

      def add_column name
        @columns << name
      end

      def add_row array
        if @columns.length != array.length
          raise "Bad row format. Expected array with length #{@columns.length}, got #{array.length}."
        end
        @rows << array
      end

      def add_divider
        @rows << DIVIDER
      end

      def add_columns cols
        cols.each { |c| add_column c }
      end

      def render max_len = 79
        s = []
        # one char will be wasted for the last (odd) vertical border '|'
        col_sizes = calculate_col_sizes(max_len-1)
        hborder = (col_sizes.collect { |size| '+'.ljust(size, '-') }).join+'+'
        #heading
        s << @name.ljust(max_len)
        s << hborder
        s << render_row(@columns, col_sizes)
        s << hborder
        rows.each do |row|
          if row == DIVIDER
            s << hborder
          else
            s << render_row(row, col_sizes)
          end
        end
        s << hborder
        s.join("\n")
      end

      def render_row row, col_sizes
        i = -1
        '| '+(row.collect { |r|
          i += 1
          if r.is_a?(String) && r[0,1] =~ /[A-Z]/
            r.to_s.center(col_sizes[i]-2)
          elsif r.is_a?(Integer) || r.is_a?(Float) || r[-1,1] == '%'
            r.to_s.rjust(col_sizes[i]-3)+' '
          else
            r.to_s.ljust(col_sizes[i]-2)
          end
        }).join('| ')+'|'
      end

      def calculate_col_sizes maxlen
        maximums, averages = count_col_maximums_and_averages()
        #puts "maximums:#{maximums.inspect}"
        #p [maximums, averages]
        avgsum = sum(averages)
        #maxsum = sum(maximums)
        sizes = averages.collect { |avg|
          (maxlen*avg/avgsum).to_i
        }
        while sum(sizes) < maxlen
          sizes[sizes.index(sizes.min)] += 1
        end
        sizes
      end

      def count_col_maximums_and_averages
        sums = @columns.collect { 0.0 }
        maximums = sums.dup
        all_rows = (@rows.dup.push @columns)
        @columns.each_index { |i|
          all_rows.each { |row|
            field = row[i]
            sums[i] += field.to_s.length
            if maximums[i] < field.to_s.length
              maximums[i] = field.to_s.length
            end
          }
        }      
        averages = sums.collect { |sum| sum*1.0/sums.length }
        [maximums, averages]
      end

      def sum a
        sum = 0
        a.each { |v| sum += v }
        sum
      end

    end
  end
end